// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Define the controller for bite.layer.QuickConstruction
 * console.  The object defines the functionality specifically for this
 * controller and does not affect the outside world.  However, it does define
 * a set of signals that can be used by external sources to perform actions
 * when the controller performs certain tasks.
 *
 * The QuickConstruction console is inteneded to have only a single instance.
 *
 * @author jasonstredwick@google.com (Jason Stredwick)
 */


goog.provide('bite.layer.QuickConstruction');
goog.provide('bite.layer.QuickConstructionEventType');

goog.require('bite.common.events.DataBearingEvent');
goog.require('bite.common.mvc.helper');
goog.require('bite.layer.ConsoleBehavior');
goog.require('bite.layer.soy.QuickConstruction');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');



/**
 * Constructs an instance of a QuickConstruction controller.  The constructor
 * creates the object in a default state that has not been mapped to a view.
 * Note: As I need to use goog.mixin to fake multiple inheritance, I believe I
 *       can only extend the console once, so I am using EventTarget for that.
 * @extends {goog.events.EventTarget}
 * @constructor
 * @export
 */
bite.layer.QuickConstruction = function() {
  goog.base(this);

  /**
   * The background javascript code textarea element.
   * @type {Element}
   * @private
   */
  this.bgJsCode_ = null;

  /**
   * The visible code textarea element.
   * @type {Element}
   * @private
   */
  this.code_ = null;

  /**
   * Console element managed by this controller.  Defaults to null meaning the
   * controller is not attached to a model.
   * @type {Element}
   * @private
   */
  this.console_ = null;

  /**
   * The content-side javascript code textarea element.
   * @type {Element}
   * @private
   */
  this.csJsCode_ = null;

  /**
   * The css code textarea element.
   * @type {Element}
   * @private
   */
  this.cssCode_ = null;

  /**
   * Contains the currently selected code entry text area.
   * @type {string}
   * @private
   */
  this.currentCode_ = bite.layer.QuickConstruction.Input_.CODE_JS_BG;

  /**
   * Whether or not the console's model is initialized.
   * @type {boolean}
   * @private
   */
  this.isModelInitialized_ = false;

  /**
   * Whether or not the console's view is initialized.
   * @type {boolean}
   * @private
   */
  this.isViewInitialized_ = false;

  /**
   * The name input element.
   * @type {Element}
   * @private
   */
  this.name_ = null;

  /**
   * The current layer's on-demand loading state.
   * @type {string}
   * @private
   */
  this.ondemand_ = bite.layer.QuickConstruction.Data.DEFAULT_VALUE;

  /**
   * The current layer's state.
   * @type {string}
   * @private
   */
  this.state_ = bite.layer.QuickConstruction.Data.DEFAULT_VALUE;
};
goog.inherits(bite.layer.QuickConstruction, goog.events.EventTarget);
goog.mixin(bite.layer.QuickConstruction.prototype,
           bite.layer.ConsoleBehavior.prototype);
goog.addSingletonGetter(bite.layer.QuickConstruction);


/**
 * The console's id.
 * @type {string}
 * @private
 */
bite.layer.QuickConstruction.CONSOLE_ID_ = 'layer-quick-construction-console';


/**
 * Element ids for the console's buttons.
 * @enum {string}
 * @private
 */
bite.layer.QuickConstruction.Button_ = {
  CLOSE: 'layer-quick-construction-close',
  RESET: 'layer-quick-construction-reset',
  SAVE: 'layer-quick-construction-save'
};


/**
 * Defines key values used to pass data in and out of the console.
 * @enum {string}
 * @export
 */
bite.layer.QuickConstruction.Data = {
  BACKGROUND_JS: 'backgroundJs',
  CONTENT_CSS: 'contentCss',
  CONTENT_JS: 'contentJs',
  DEFAULT_VALUE: 'default',
  EDIT: 'edit',
  NAME: 'name',
  ONDEMAND: 'ondemand',
  STATE: 'state'
};


/**
 * Element ids for the console's inputs.
 * @enum {string}
 * @private
 */
bite.layer.QuickConstruction.Input_ = {
  // Code Text Boxes and Controller
  CODE: 'layer-quick-construction-code',
  CODE_CSS: 'layer-quick-construction-css',
  CODE_JS_BG: 'layer-quick-construction-js-bg',
  CODE_JS_CS: 'layer-quick-construction-js-cs',
  CODE_SELECT: 'layer-quick-construction-code-select',

  // Layer Name Text Box
  NAME: 'layer-quick-construction-name',

  // On-demand Loading Radio Buttons
  ONDEMAND_NEVER: 'layer-quick-construction-ondemand-never',
  ONDEMAND_OFF: 'layer-quick-construction-ondemand-off',
  ONDEMAND_ON: 'layer-quick-construction-ondemand-on',

  // Layer State Radio Buttons
  STATE_ALWAYS: 'layer-quick-construction-enabled-always',
  STATE_OFF: 'layer-quick-construction-enabled-off',
  STATE_ON: 'layer-quick-construction-enabled-on'
};


/**
 * Radio button mappings to layer appropriate values.
 * @enum {string}
 * @private
 */
bite.layer.QuickConstruction.Radio_ = {
  ALWAYS: 'fixed',
  NEVER: 'fixed',
  OFF: 'off',
  ON: 'on'
};


/**
 * Event types supported by this console.
 * @enum {string}
 * @export
 */
bite.layer.QuickConstructionEventType = {
  CLOSE: 'close',
  RESET: 'reset',
  SAVE: 'save'
};


/**
 * Handler that is called when the close button is pressed.
 * @private
 */
bite.layer.QuickConstruction.prototype.handleOnClose_ = function() {
  var event = new bite.common.events.DataBearingEvent(
      bite.layer.QuickConstructionEventType.CLOSE);
  this.dispatchEvent(event);
};


/**
 * Handler that is called when the code text entry drop down menu changes.  It
 * saves the code in the currently selected, hidden text area.  It then clears
 * the visible code text area, and adds the code from the newly selected code's
 * hidden text area value.
 * @param {Event} event The DOM Event that is triggered when the select
 *     element is changed.  This handler is attached to a select element and
 *     will contain a variable called selectedIndex that corresponds to index
 *     within the selection box using zero based array notation.
 * @private
 */
bite.layer.QuickConstruction.prototype.handleOnChangeCode_ = function(event) {
  this.updateSelectedCode_();

  // Set the current type of code and retrieve it's text.
  var selectedIndex = event.target.selectedIndex;
  switch (selectedIndex) {
    case 1:
      this.currentCode_ = bite.layer.QuickConstruction.Input_.CODE_JS_CS;
      break;
    case 2:
      this.currentCode_ = bite.layer.QuickConstruction.Input_.CODE_CSS;
      break;
    case 0:
    default:
      this.currentCode_ = bite.layer.QuickConstruction.Input_.CODE_JS_BG;
      break;
  }

  this.updateVisibleCode_();
};


/**
 * Handler that is called when the reset button is pressed.
 * @private
 */
bite.layer.QuickConstruction.prototype.handleOnReset_ = function() {
  var event = new bite.common.events.DataBearingEvent(
      bite.layer.QuickConstructionEventType.RESET);
  this.dispatchEvent(event);
};


/**
 * Handler that is called when the save button is pressed.  It scans the
 * inputs, does some minor validation, and passes on the information for
 * external sources.
 * @private
 */
bite.layer.QuickConstruction.prototype.handleOnSave_ = function() {
  if (!bite.common.mvc.helper.validateName(this.name_.value)) {
    return;
  }

  // Make sure and save the current text in the appropriate code text area
  // before reading the code text area values.
  this.updateSelectedCode_();

  var data = {};
  data[bite.layer.QuickConstruction.Data.NAME] = this.name_.value;
  data[bite.layer.QuickConstruction.Data.STATE] = this.state_;
  data[bite.layer.QuickConstruction.Data.ONDEMAND] = this.ondemand_;
  data[bite.layer.QuickConstruction.Data.BACKGROUND_JS] =
      this.bgJsCode_.value;
  data[bite.layer.QuickConstruction.Data.CONTENT_JS] = this.csJsCode_.value;
  data[bite.layer.QuickConstruction.Data.CONTENT_CSS] = this.cssCode_.value;

  var event = new bite.common.events.DataBearingEvent(
      bite.layer.QuickConstructionEventType.SAVE, data);
  this.dispatchEvent(event);
};


/**
 * Set the ondemand_ value to the given value.
 * @param {string} state The new state.
 * @private
 */
bite.layer.QuickConstruction.prototype.handleSetOndemand_ = function(state) {
  this.ondemand_ = state;
};


/**
 * Set the state_ value to the given.
 * @param {string} state The new state.
 * @private
 */
bite.layer.QuickConstruction.prototype.handleSetState_ = function(state) {
  this.state_ = state;
};


/**
 * Initialize the QuickConstruction Console by creating the model and hooking
 * functions to the model.  If a baseUrl is given and the view is not yet
 * initialized then this function will try to initialize it.  Nothing happens
 * if the console is already initialized.
 * @param {string=} opt_baseUrl The baseUrl required for relative references.
 * @return {boolean} Whether or not the model is initialized.  It will also
 *     return true if the model is already initialized.
 * @export
 */
bite.layer.QuickConstruction.prototype.init = function(opt_baseUrl) {
  // Checking isDef allows for baseUrl to be the empty string.
  if (goog.isDef(opt_baseUrl) && !this.isViewInitialized_) {
    this.initView(opt_baseUrl);
  }

  if (this.isModelInitialized_) {
    return true;
  }

  var helper = bite.common.mvc.helper;
  this.console_ = helper.initModel(bite.layer.soy.QuickConstruction.getModel);

  if (this.console_ &&
      this.setupBehavior_(this.console_) &&
      this.setupButtonHandlers_(this.console_) &&
      this.setupRadioHandlers_(this.console_) &&
      this.setupTextInputs_(this.console_)) {
    this.isModelInitialized_ = true;
  } else {
    this.undoSetup_();
  }

  return this.isModelInitialized_;
};


/**
 * Given an optional baseUrl, initialize the view for the console by adding
 * its css to the page.
 * @param {string=} opt_baseUrl The baseUrl required for relative references.
 * @return {boolean} Whether or not the view was added.  It also returns true
 *     if the view is already initialized.
 * @export
 */
bite.layer.QuickConstruction.prototype.initView = function(opt_baseUrl) {
  var helper = bite.common.mvc.helper;
  var qc = bite.layer.soy.QuickConstruction;
  if (!this.isViewInitialized_ && helper.initView(qc.getView, opt_baseUrl)) {
    this.isViewInitialized_ = true;
  }

  return this.isViewInitialized_;
};


/**
 * Determines if the console is constructed or not.
 * @return {boolean} Whether or not the console is initialized.
 * @export
 */
bite.layer.QuickConstruction.prototype.isInitialized = function() {
  return this.isViewInitialized_ && this.isModelInitialized_;
};


/**
 * Takes an object that contains data for the various inputs and sets the
 * input elements to the appropriate values.
 * @param {{name: string, edit: string, ondemand: string, state: string,
 *          bgJsCode: string, csJsCode: string, cssCode: string}=} opt_data The
 *     data used to fill out the form.
 * @return {boolean} Whether or not the console was updated.
 * @export
 */
bite.layer.QuickConstruction.prototype.reset = function(opt_data) {
  if (!this.isInitialized()) {
    return false;
  }

  // Ensure that data is an object in case if was null, undefined, or something
  // else.
  var data = opt_data || {};

  this.currentCode_ = bite.layer.QuickConstruction.Input_.CODE_JS_BG;
  this.ondemand_ = bite.layer.QuickConstruction.Data.DEFAULT_VALUE;
  this.state_ = bite.layer.QuickConstruction.Data.DEFAULT_VALUE;

  // Ignore state and ondemand values, as update will check the state
  // variables for the default status if they are not present.
  data[bite.layer.QuickConstruction.Data.NAME] =
      data[bite.layer.QuickConstruction.Data.NAME] || '';
  data[bite.layer.QuickConstruction.Data.EDIT] =
      data[bite.layer.QuickConstruction.Data.EDIT] || 'true';
  data[bite.layer.QuickConstruction.Data.BACKGROUND_JS] =
      data[bite.layer.QuickConstruction.Data.BACKGROUND_JS] || '';
  data[bite.layer.QuickConstruction.Data.CONTENT_JS] =
      data[bite.layer.QuickConstruction.Data.CONTENT_JS] || '';
  data[bite.layer.QuickConstruction.Data.CONTENT_CSS] =
      data[bite.layer.QuickConstruction.Data.CONTENT_CSS] || '';

  data = /** @type {{name: string, edit: string, ondemand: string,
                     state: string, bgJsCode: string, csJsCode: string,
                     cssCode: string}} */ (data);
  return this.update(data);
};


/**
 * Setup an element to be controlled by the behaviors.
 * @param {!Element} srcElement The element to control.
 * @return {boolean} Setup was successful.
 * @private
 */
bite.layer.QuickConstruction.prototype.setupBehavior_ = function(srcElement) {
  var obj = /** @type {Object} */ (this);
  obj.setBehavior(srcElement);
  obj.hide();
  obj.disable();

  return true;
};


/**
 * Setup buttons in console.
 * @param {!Element} srcElement The element to search from.
 * @return {boolean} Successfully found and attached handlers.
 * @private
 */
bite.layer.QuickConstruction.prototype.setupButtonHandlers_ =
    function(srcElement) {
  var helper = bite.common.mvc.helper;
  var input = bite.layer.QuickConstruction.Input_;

  var select = helper.getElement(input.CODE_SELECT, srcElement);
  goog.events.listen(select, goog.events.EventType.CHANGE,
                     goog.bind(this.handleOnChangeCode_, this));

  var button = bite.layer.QuickConstruction.Button_;
  var close = helper.getElement(button.CLOSE, srcElement);
  goog.events.listen(close, goog.events.EventType.CLICK,
                     goog.bind(this.handleOnClose_, this));

  var reset = helper.getElement(button.RESET, srcElement);
  goog.events.listen(reset, goog.events.EventType.CLICK,
                     goog.bind(this.handleOnReset_, this));

  var save = helper.getElement(button.SAVE, srcElement);
  goog.events.listen(save, goog.events.EventType.CLICK,
                     goog.bind(this.handleOnSave_, this));

  return true;
};


/**
 * Setup buttons in console.
 * @param {!Element} srcElement The element to search from.
 * @return {boolean} Successfully found and attached handlers.
 * @private
 */
bite.layer.QuickConstruction.prototype.setupRadioHandlers_ =
    function(srcElement) {
  var helper = bite.common.mvc.helper;
  var input = bite.layer.QuickConstruction.Input_;

  var ondemandNever = helper.getElement(input.ONDEMAND_NEVER, srcElement);
  goog.events.listen(ondemandNever, goog.events.EventType.CLICK,
      goog.bind(this.handleSetOndemand_, this,
                bite.layer.QuickConstruction.Radio_.NEVER));

  var ondemandOff = helper.getElement(input.ONDEMAND_OFF, srcElement);
  goog.events.listen(ondemandOff, goog.events.EventType.CLICK,
      goog.bind(this.handleSetOndemand_, this,
                bite.layer.QuickConstruction.Radio_.OFF));

  var ondemandOn = helper.getElement(input.ONDEMAND_ON, srcElement);
  goog.events.listen(ondemandOn, goog.events.EventType.CLICK,
      goog.bind(this.handleSetOndemand_, this,
                bite.layer.QuickConstruction.Radio_.ON));

  var stateAlways = helper.getElement(input.STATE_ALWAYS, srcElement);
  goog.events.listen(stateAlways, goog.events.EventType.CLICK,
      goog.bind(this.handleSetState_, this,
                bite.layer.QuickConstruction.Radio_.ALWAYS));

  var stateOff = helper.getElement(input.STATE_OFF, srcElement);
  goog.events.listen(stateOff, goog.events.EventType.CLICK,
      goog.bind(this.handleSetState_, this,
                bite.layer.QuickConstruction.Radio_.OFF));

  var stateOn = helper.getElement(input.STATE_ON, srcElement);
  goog.events.listen(stateOn, goog.events.EventType.CLICK,
      goog.bind(this.handleSetState_, this,
                bite.layer.QuickConstruction.Radio_.ON));

  return true;
};


/**
 * Setup input elements from the newly created console and store them.
 * @param {!Element} srcElement The element to search from.
 * @return {boolean} Successfully found elements or not.
 * @private
 */
bite.layer.QuickConstruction.prototype.setupTextInputs_ =
    function(srcElement) {
  var helper = bite.common.mvc.helper;
  var input = bite.layer.QuickConstruction.Input_;

  this.code_ = helper.getElement(input.CODE, srcElement);
  this.bgJsCode_ = helper.getElement(input.CODE_JS_BG, srcElement);
  this.csJsCode_ = helper.getElement(input.CODE_JS_CS, srcElement);
  this.cssCode_ = helper.getElement(input.CODE_CSS, srcElement);
  this.name_ = helper.getElement(input.NAME, srcElement);

  return true;
};


/**
 * Reset the model portion of the controller back to an uninitialized state.
 * @private
 */
bite.layer.QuickConstruction.prototype.undoSetup_ = function() {
  // Make sure and remove the DOM Element from the document upon failure.
  if (this.console_) {
    var helper = bite.common.mvc.helper;
    helper.removeElementById(bite.layer.QuickConstruction.CONSOLE_ID_);
  }

  this.bgJsCode_ = null;
  this.code_ = null;
  this.console_ = null;
  this.csJsCode_ = null;
  this.cssCode_ = null;
  this.currentCode_ = bite.layer.QuickConstruction.Input_.CODE_JS_BG;
  this.isModelInitialized_ = false;
  this.name_ = null;
  this.ondemand_ = bite.layer.QuickConstruction.Data.DEFAULT_VALUE;
  this.state_ = bite.layer.QuickConstruction.Data.DEFAULT_VALUE;
};


/**
 * Takes an object that contains data for the various inputs and sets the
 * input elements to the appropriate values.
 * @param {{name: string, edit: string, ondemand: string, state: string,
 *          bgJsCode: string, csJsCode: string, cssCode: string}} data The
 *     data used to fill out the form.
 * @return {boolean} Whether or not the console was updated.
 * @export
 */
bite.layer.QuickConstruction.prototype.update = function(data) {
  if (!this.isInitialized()) {
    return false;
  }

  var srcElement = /** @type {!Element} */ (this.console_);

  // Find radio button elements.
  var helper = bite.common.mvc.helper;
  var input = bite.layer.QuickConstruction.Input_;

  var ondemandNever = helper.getElement(input.ONDEMAND_NEVER, srcElement);
  var ondemandOff = helper.getElement(input.ONDEMAND_OFF, srcElement);
  var ondemandOn = helper.getElement(input.ONDEMAND_ON, srcElement);

  var stateAlways = helper.getElement(input.STATE_ALWAYS, srcElement);
  var stateOff = helper.getElement(input.STATE_OFF, srcElement);
  var stateOn = helper.getElement(input.STATE_ON, srcElement);

  // Ensure that data is an object in case if was null, undefined, or something
  // else.
  data = data || {};

  // Set name
  if (bite.layer.QuickConstruction.Data.NAME in data) {
    this.name_.value = data[bite.layer.QuickConstruction.Data.NAME];
  }

  // Check if name input is editable
  if (bite.layer.QuickConstruction.Data.EDIT in data) {
    this.name_.disabled =
        data[bite.layer.QuickConstruction.Data.EDIT] == 'true' ? undefined :
                                                                 'disabled';
  }

  // Set code values
  if (bite.layer.QuickConstruction.Data.BACKGROUND_JS in data) {
    this.bgJsCode_.value =
        data[bite.layer.QuickConstruction.Data.BACKGROUND_JS];
  }
  if (bite.layer.QuickConstruction.Data.CONTENT_JS in data) {
    this.csJsCode_.value = data[bite.layer.QuickConstruction.Data.CONTENT_JS];
  }
  if (bite.layer.QuickConstruction.Data.CONTENT_CSS in data) {
    this.cssCode_.value = data[bite.layer.QuickConstruction.Data.CONTENT_CSS];
  }

  this.updateVisibleCode_();

  // Set state radio buttons.
  this.updateRadios_(data[bite.layer.QuickConstruction.Data.STATE],
      this.state_, stateAlways, stateOff, stateOn,
      goog.bind(this.handleSetState_, this));

  // Set ondemand radio buttons.
  this.updateRadios_(data[bite.layer.QuickConstruction.Data.ONDEMAND],
      this.ondemand_, ondemandNever, ondemandOff, ondemandOn,
      goog.bind(this.handleSetOndemand_, this));

  return true;
};


/**
 * Given a choice, set the appropriate radio button to checked or turn them
 * all off when radio group is in a default state and no choice is given.
 * @param {string} value The external choice.
 * @param {string} currentChoice The current choice.
 * @param {Element} radioFixed The radio representing the fixed state.
 * @param {Element} radioOff The radio representing the off state.
 * @param {Element} radioOn The radio representing the on state.
 * @param {function(string)} handler The handler to update the change.
 * @private
 */
bite.layer.QuickConstruction.prototype.updateRadios_ =
    function(value, currentChoice, radioFixed, radioOff, radioOn, handler) {
  var element = undefined;

  switch (value) {
    case bite.layer.QuickConstruction.Radio_.ALWAYS:
    case bite.layer.QuickConstruction.Radio_.NEVER:
      element = radioFixed;
      break;
    case bite.layer.QuickConstruction.Radio_.OFF:
      element = radioOff;
      break;
    case bite.layer.QuickConstruction.Radio_.ON:
      element = radioOn;
      break;
    default:
      // Radio button selection should not change if a value isn't given unless
      // the currentChoice is set to default in which case they are all
      // unchecked.
      if (currentChoice == bite.layer.QuickConstruction.Data.DEFAULT_VALUE) {
        radioFixed.checked = undefined;
        radioOff.checked = undefined;
        radioOn.checked = undefined;
      }

      return;
  }

  if (element) {
    element.checked = 'checked';
    handler(value);
  }
};


/**
 * Set the currently selected code type to the text in the code text area.
 * @private
 */
bite.layer.QuickConstruction.prototype.updateSelectedCode_ = function() {
  var text = this.code_.value;
  switch (this.currentCode_) {
    case bite.layer.QuickConstruction.Input_.CODE_JS_BG:
      this.bgJsCode_.value = text;
      break;
    case bite.layer.QuickConstruction.Input_.CODE_JS_CS:
      this.csJsCode_.value = text;
      break;
    case bite.layer.QuickConstruction.Input_.CODE_CSS:
      this.cssCode_.value = text;
      break;
    default:
      return;
  }
};


/**
 * Set the visible code to reflect the currently selected code's text.
 * @private
 */
bite.layer.QuickConstruction.prototype.updateVisibleCode_ = function() {
  var text = '';
  switch (this.currentCode_) {
    case bite.layer.QuickConstruction.Input_.CODE_JS_BG:
      text = this.bgJsCode_.value;
      break;
    case bite.layer.QuickConstruction.Input_.CODE_JS_CS:
      text = this.csJsCode_.value;
      break;
    case bite.layer.QuickConstruction.Input_.CODE_CSS:
      text = this.cssCode_.value;
      break;
    default:
      console.log('WARNING (bite.layer.QuickConstruction.update): Unknown' +
                  'code selection.');
  }

  this.code_.value = text;
};

